////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#pragma once

#ifndef __WN_CONCURRENCY_THREAD_INL__
#define __WN_CONCURRENCY_THREAD_INL__

#include "WNCore/inc/WNPlatform.h"
#include "WNCore/inc/WNTypes.h"
#include "WNCore/inc/WNAssert.h"
#include "WNCore/inc/WNSystem.h"

#ifdef _WN_WINDOWS
    #include <windows.h>
#elif defined _WN_LINUX
    #include "WNCore/inc/WNMacros.h"

    #include <unistd.h>
    #include <pthread.h>
    #include <time.h>
    #include <sys/syscall.h>
    #include <errno.h>
    #include <sched.h>
#endif

#define WN_THREAD_INVALID 0
#define WN_THREAD_READY 1
#define WN_THREAD_RUNNING 2
#define WN_THREAD_FINISHED 3

namespace WNConcurrency {
    WN_INLINE WNThread::WNThread() :
        #ifdef _WN_WINDOWS
            mHandle(NULL),
        #endif

        mFunction(WN_NULL),
        mParameters(WN_NULL),
        mReturn(WN_NULL),
        mID(-1),
        mState(WN_THREAD_INVALID),
        mAffinity(0),
        mPriority(0) {
        for (WN_SIZE_T i = 0; i < WNSystem::LogicalThreadCount(); ++i) {
            WN_BIT_SET(mAffinity, i);
        }
    }

    WN_INLINE WNThread::WNThread(WN_THREAD_FUNCTION_T _function, WN_VOID* _parameters) :
        #ifdef _WN_WINDOWS
            mHandle(NULL),
        #endif

        mFunction(_function),
        mParameters(_parameters),
        mReturn(WN_NULL),
        mID(-1),
        mState(WN_THREAD_INVALID),
        mAffinity(~0),
        mPriority(0) {
        for (WN_SIZE_T i = 0; i < WNSystem::LogicalThreadCount(); ++i) {
            WN_BIT_SET(mAffinity, i);
        }
    }

    WN_INLINE WNThread::~WNThread() {
        #ifdef _WN_WINDOWS
            if (mHandle != NULL) {
                WN_RELEASE_ASSERT(CloseHandle(mHandle));
            }
        #endif
    }

    WN_INLINE WN_BOOL WNThread::SetStartRoutine(WN_THREAD_FUNCTION_T _function, WN_VOID* _parameters) {
        if (_function == WN_NULL) {
            return(WN_FALSE);
        }

        if (IsRunning() || IsFinished()) {
            return(WN_FALSE);
        }

        if (mFunction != WN_NULL) {
            return(WN_FALSE);
        }

        mFunction = _function;
        mParameters = _parameters;

        return(WN_TRUE);
    }

    WN_INLINE WN_BOOL WNThread::Execute() {
        if (IsRunning() || IsFinished()) {
            return(WN_FALSE);
        }

        if (mFunction == WN_NULL) {
            return(WN_FALSE);
        }

        #ifdef _WN_WINDOWS
            mHandle = CreateThread(NULL, 0, &Starter, this, 0, reinterpret_cast<LPDWORD>(&mID));

            if (mHandle == NULL) {
                return(WN_FALSE);
            }
        #elif defined _WN_LINUX
            pthread_attr_t attributes;

            if (pthread_attr_init(&attributes) == 0) {
                pthread_t thread;

                const WN_UINT32 result = pthread_create(&thread, &attributes, &Starter, this);

                pthread_attr_destroy(&attributes);

                if (result != 0) {
                    return(WN_FALSE);
                }
                mThread = thread;
            } else {
                return(WN_FALSE);
            }
        #endif

        mState = WN_THREAD_READY;

        SetAffinity(mAffinity);
        SetPriority(mPriority);

        return(WN_TRUE);
    }

    WN_BOOL WNThread::SetAffinity(WN_SIZE_T _affinity) {
        mAffinity = _affinity;

        #ifdef _WN_WINDOWS
            if (mHandle != WN_NULL) {
                if (SetThreadAffinityMask(mHandle, mAffinity) == 0) {
                    return(WN_FALSE);
                }
            }
        #elif defined _WN_LINUX
            if (mState >= 1) {
                cpu_set_t affinity;

                CPU_ZERO(&affinity);

                for (WN_SIZE_T i = 0; i < WN_BYTES_TO_BITS(sizeof(WN_SIZE_T)); ++i) {
                    if (((mAffinity >> i) & 1) == 1) {
                        CPU_SET(i, &affinity);
                    }
                }

                if (pthread_setaffinity_np(mThread, sizeof(cpu_set_t), &affinity) != 0) {
                    return(WN_FALSE);
                }
            }
        #endif

        return(WN_TRUE);
    }

    WN_INLINE WN_BOOL WNThread::SetPriority(WN_UINT32 _priority) {
        mPriority = _priority;

        #ifdef _WN_WINDOWS
            if (mHandle != WN_NULL) {
                return(SetThreadPriority(mHandle, mPriority) != 0 ? WN_TRUE : WN_FALSE);
            }
        #elif defined _WN_LINUX
            return(WN_FALSE); // need to figure out
        #endif

        return(WN_TRUE);
    }

    WN_INLINE WN_SIZE_T WNThread::GetAffinity() const {
        return(mAffinity);
    }

    WN_INLINE WN_UINT32 WNThread::GetPriority() const {
        return(mPriority);
    }

    WN_INLINE WN_BOOL WNThread::GetReturn(WN_VOID** _return) const {
        if (IsFinished()) {
            *_return = mReturn;

            return(WN_TRUE);
        }

        return(WN_FALSE);
    }

    WN_INLINE WN_UINT32 WNThread::GetID() const {
        return(mID);
    }

    WN_INLINE WN_BOOL WNThread::Join(WN_UINT32 _milliseconds) const {
        WN_DEBUG_ASSERT(_milliseconds != 0);

        #ifdef _WN_WINDOWS
            if (WaitForSingleObject(mHandle, _milliseconds) == WAIT_FAILED) {
                return(WN_FALSE);
            }
        #elif defined _WN_LINUX
            if (_milliseconds >= 0xFFFFFFFF) {
                if (pthread_join(mThread, WN_NULL) != 0) {
                    return(WN_FALSE);
                }
            } else {
                struct timespec timeOut = {0};

                timeOut.tv_sec = _milliseconds / 1000;
                timeOut.tv_nsec = (_milliseconds % 1000) * 1000000L;

                if (pthread_timedjoin_np(mThread, WN_NULL, &timeOut) != 0) {
                    return(WN_FALSE);
                }
            }
        #endif

        return(WN_TRUE);
    }

    WN_INLINE WN_BOOL WNThread::IsValid() const {
        return(mState != WN_THREAD_INVALID);
    }

    WN_INLINE WN_BOOL WNThread::IsReady() const {
        return(mState == WN_THREAD_READY);
    }

    WN_INLINE WN_BOOL WNThread::IsRunning() const {
        return(mState == WN_THREAD_RUNNING);
    }

    WN_INLINE WN_BOOL WNThread::IsFinished() const {
        return(mState == WN_THREAD_FINISHED);
    }

    #ifdef _WN_WINDOWS
        WN_INLINE DWORD WINAPI WNThread::Starter(WN_VOID* _thread) {
            WNThread* thread = reinterpret_cast<WNThread*>(_thread);

            thread->mState = WN_THREAD_RUNNING;
            thread->mReturn = thread->mFunction(thread->mParameters);
            thread->mState = WN_THREAD_FINISHED;

            return(0);
        }
    #elif defined _WN_LINUX
        WN_INLINE WN_VOID* WNThread::Starter(WN_VOID* _thread) {
            WNThread* thread = reinterpret_cast<WNThread*>(_thread);

            thread->mID = static_cast<WN_UINT32>(syscall(SYS_gettid));
            thread->mState = WN_THREAD_RUNNING;
            thread->mReturn = thread->mFunction(thread->mParameters);
            thread->mState = WN_THREAD_FINISHED;

            return(WN_NULL);
        }
    #endif

    WN_INLINE WN_VOID WNThreadYield() {
        if (WNSystem::LogicalThreadCount() > 1) {
            WN_YIELD_PROCESSOR();
        } else{
            #ifdef _WN_WINDOWS
                SwitchToThread();
            #elif defined _WN_LINUX
                pthread_yield();
            #endif
        }
    }

    WN_INLINE WN_VOID WNThreadSleep(WN_UINT32 _milliseconds) {
        #ifdef _WN_WINDOWS
            Sleep(_milliseconds);
        #elif defined _WN_LINUX
            struct timespec timeRequested = {0};

            timeRequested.tv_sec = _milliseconds / 1000;
            timeRequested.tv_nsec = (_milliseconds % 1000) * 1000000L;

            while (nanosleep(&timeRequested, &timeRequested) == -1) {
                continue;
            }
        #endif
    }
}

#endif // __WN_CONCURRENCY_THREAD_INL__
